import sys
import os
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QMessageBox, QTableWidgetItem
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSlot, pyqtSignal
import pandas as pd
import akshare as ak
from datetime import date
from typing import Callable

from pts.task_engine import TaskEngine, Task
from pts.constant import TaskType
from .Ui_gn_fundflow import Ui_GnFundFlow
from ..setting import SETTINGS
from ..global_functions import displayDataFrame, getAmountFromValue
from ..database import OpenGnBlockDb
from ..web_crawler import stock_sector_fund_flow_rank, stock_sector_fund_flow_his
import pts.global_variable as glv

# 导入matplotlib模块并使用Qt5Agg
import matplotlib
matplotlib.use('Qt5Agg')
# 使用 matplotlib中的FigureCanvas (在使用 Qt5 Backends中 FigureCanvas继承自QtWidgets.QWidget)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt


class GnFundFlow(QWidget, Ui_GnFundFlow):
    """5-概论板块资金流 页面"""
    # 当日和5日板块资金流的文件名
    file_gnfundflow1 = r'Data\gnfundflow1.csv'
    file_gnfundflow5 = r'Data\gnfundflow5.csv'

    # 用于处理自动刷新完成信息的信号
    signal: pyqtSignal = pyqtSignal(str)

    def __init__(self, task_engine: TaskEngine, displayStatusMess: Callable):
        """初始化"""
        super(GnFundFlow, self).__init__()
        self.setupUi(self)

        # 从主窗口接受到的任务引擎
        self.task_engine: TaskEngine = task_engine
        # 主窗口的信息显示函数
        self.displayStatusMess: Callable = displayStatusMess

        # 初始化界面
        self.initUi()
        # 初始化与本页面相关的任务
        self.initTask()

    def initTask(self):
        """初始化与本页面相关的任务"""
        # 盘中任务
        task = Task(TaskType.pz, self.process_task, self.signal.emit, interval=SETTINGS["RW.spPZ_5"])
        self.task_engine.put(task)

        # 盘后任务
        task = Task(TaskType.ph, self.process_task, self.signal.emit, task_id=5)
        self.task_engine.put(task)

        # 为信号关联槽函数
        self.signal.connect(self.display_task)

    def process_task(self) -> str:
        """
        任务的执行函数，供任务引擎的守护线程调用
        :return: 任务执行的结果信息
        """
        return self.reloadGNFundFlow()

    def display_task(self, mess: str):
        """
        任务执行结果的显示函数，供任务引擎的守护线程调用
        :param mess: 任务执行的结果信息
        :return: 无
        """
        # 在主窗口的状态栏上显示结果信息
        self.displayStatusMess(mess)
        # 更新页面上的列表
        # 显示当日和5日板块资金流
        self.displayGNFundFlow()

    def initUi(self):
        """初始化界面"""
        # 初始化历史列表的表头及图表
        self.initChart()

        # 显示当日和5日板块资金流
        self.displayGNFundFlow()

    def initChart(self):
        """初始化历史列表的表头及图表"""
        # 初始化历史列表的表头
        headers = ['日期', '涨幅', '主力净流入', '超大单净流入', '大单净流入', '中单净流入', '小单净流入']
        cnum = len(headers)
        self.twGNFundFlowHistory.setColumnCount(cnum)
        self.twGNFundFlowHistory.setHorizontalHeaderLabels(headers)

        # 初始化图表
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)

        # 在Figure对象中创建一个Axes对象，每个Axes对象即为一个绘图区域
        self.ax = self.figure.add_subplot(111)

        # 将画布加入到Widget
        self.hbox = QHBoxLayout()
        self.hbox.addWidget(self.canvas)
        self.tabFundFlowChart.setLayout(self.hbox)


    def displayGNFundFlow(self):
        """显示当日和5日板块资金流"""
        # 如果数据文件不存在，重新下载
        if not os.path.exists(self.file_gnfundflow1) or not os.path.exists(self.file_gnfundflow5):
            self.reloadGNFundFlow()

        # 从数据文件中读取数据
        stock_data1 = pd.read_csv(self.file_gnfundflow1)
        stock_data5 = pd.read_csv(self.file_gnfundflow5)

        # 同时存在于当日和5日资金流前20名的板块
        set1 = set(stock_data1['名称'].values[:SETTINGS["block.number"]])
        set5 = set(stock_data5['名称'].values[:SETTINGS["block.number"]])
        self.GNBlocks = set1 & set5

        # 显示当日和5日资金流
        displayDataFrame(stock_data1, self.twGNFundFlow1, startCol=1, bkKind=2, simuList=self.GNBlocks, amountList=[3, 5, 7, 9, 11], orderCol=[3, ])
        displayDataFrame(stock_data5, self.twGNFundFlow5, startCol=1, bkKind=2, simuList=self.GNBlocks, amountList=[3, 5, 7, 9, 11], orderCol=[3, ])

    def reloadOneGNHis(self, block_name: str, block_code: str):
        """对于当前板块：重新下载历史数据"""
        # 重新下载当日和5日的板块资金流向，并存入CSV文件
        stock_data = None
        try:
            stock_data = stock_sector_fund_flow_his(block_code, "板块")
        except BaseException as ex:
            QMessageBox.information(self, '提示信息', '数据下载失败：{}。'.format(ex.args))
        if stock_data is None:
            return

        # 将当日的存入数据库
        conn = OpenGnBlockDb()
        if not conn:
            QMessageBox.information(self, '提示信息', '打开数据库失败。')

        sqlStr = "insert into GnBlock values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
        try:
            # 先删除原有数据
            da1 = stock_data.iloc[:, 0].dropna().min()
            da2 = stock_data.iloc[:, 0].dropna().max()
            conn.execute("delete from GnBlock where mc = ? and rq >= ? and rq <= ?", (block_name, da1, da2))

            # 插入新数据
            for index, row in stock_data.iterrows():
                record = (row[0], block_name, row[12], row[1], row[6], row[5], row[10], row[4], row[9], row[3], row[8], row[2], row[7])
                conn.execute(sqlStr, record)
            conn.commit()
        except BaseException as ex:
            QMessageBox.information(self, '提示信息', '向数据库插入数据失败：{}。'.format(ex.args))
        conn.close()

        QMessageBox.information(self, '提示信息', '刷新资金流向完成。')

    def reloadOneGNList(self, block_name: str):
        """对于当前板块：重新下载成分股列表，并存入CSV文件"""
        file_name = r'Data\GNBlock\{}.csv'.format(block_name)
        stock_data = ak.stock_board_concept_cons_em(symbol=block_name)
        stock_data.to_csv(file_name, index=False)

    def reloadGNFundFlow(self) -> str:
        """对于所有板块：重新下载当日和5日的板块资金流向"""
        # 重新下载当日和5日的板块资金流向，并存入CSV文件
        stock_data = None
        try:
            stock_data = stock_sector_fund_flow_rank(indicator="5日", sector_type="概念资金流")
            stock_data.to_csv(self.file_gnfundflow5, index=False)

            stock_data = stock_sector_fund_flow_rank(indicator="今日", sector_type="概念资金流")
            stock_data.to_csv(self.file_gnfundflow1, index=False)
        except BaseException as ex:
            return '数据下载失败：{}。'.format(ex.args)

        # 将当日的存入数据库
        conn = OpenGnBlockDb()
        if not conn:
            return '打开数据库失败。'

        sqlStr = "insert into GnBlock values (?,?,?,?,?,?,?,?,?,?,?,?,?)"
        da = date.today()                    # 当前日期
        try:
            # 先删除原有数据
            conn.execute("delete from GnBlock where rq=?", (da,))

            # 插入新数据
            for index, row in stock_data.iterrows():
                record = (da, row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12])
                conn.execute(sqlStr, record)
            conn.commit()
        except BaseException as ex:
            conn.close()
            return '向数据库插入数据失败：{}。'.format(ex.args)
        conn.close()

        return '刷新概念板块资金流向完成。'

    def displayOneGNList(self, block_name: str):
        """显示某个板块内的成分股"""
        file_name = r'Data\GNBlock\{}.csv'.format(block_name)
        if not os.path.exists(file_name):
            self.twGNList.setRowCount(0)
            return

        stock_data = pd.read_csv(file_name, dtype={'代码': str})
        displayDataFrame(stock_data, self.twGNList, startCol=1, amountList=[7], orderCol=[4,])

    def displayCurGNBlock(self, reload: int):
        """显示某个板块的信息
        reload: 1-刷新资金历史；2-刷新成分股；0-不刷新"""

        # 取当前板块名称
        row = self.twGNFundFlow1.currentRow()
        if row < 0:
            self.twGNList.setRowCount(0)
            self.twGNFundFlowHistory.setRowCount(0)
            plt.cla()
            return

        item = self.twGNFundFlow1.item(row, 0)
        block_name = item.text()

        # 如果需要，从网上爬取数据
        if reload == 1:
            # 取板块编号
            item = self.twGNFundFlow1.item(row, self.twGNFundFlow1.columnCount() - 1)
            block_code = item.text()
            # 重新下载某个板块的资金历史，并存入数据库
            self.reloadOneGNHis(block_name, block_code)
            # QMessageBox.information(self, '提示信息', '数据下载完成。')
        elif reload == 2:
            # 重新下载某个板块的成分股列表，并存入CSV文件
            self.reloadOneGNList(block_name)
            # QMessageBox.information(self, '提示信息', '数据下载完成。')

        # 显示成分股
        self.displayOneGNList(block_name)

        # 从数据库中取历史资金
        conn = OpenGnBlockDb()
        if not conn:
            QMessageBox.information(self, '提示信息', '打开数据库失败。')

        try:
            # 从数据库中取某个板块多日的资金信息
            sqlStr = "select * from GnBlock where mc='{}' order by rq desc".format(block_name)
            cursor = conn.execute(sqlStr)

            # fields = ['rq', 'zf', 'zlje', 'cdde', 'dde', 'zde', 'xde']
            fields = [0, 2, 3, 5, 7, 9, 11]
            index = 0
            xs = []
            ysr = []
            ysg = []
            for row in cursor:
                # 显示到列表中
                self.twGNFundFlowHistory.setRowCount(index + 1)
                for fi in range(len(fields)):
                    istr = str(row[fields[fi]])
                    if fi > 1:
                        istr = getAmountFromValue(row[fields[fi]])
                    item = QTableWidgetItem(istr)
                    if fi == 2:
                        try:
                            va = float(row[fields[fi]])
                            if va > 0:
                                item.setForeground(QtGui.QColor(255, 0, 0))
                            else:
                                item.setForeground(QtGui.QColor(0, 255, 0))
                        except BaseException as ex:
                            pass

                    self.twGNFundFlowHistory.setItem(index, fi, item)

                # 图表上只显示最近若干天的数据
                index = index + 1
                if index > 20:
                    continue

                # 加入到坐标轴数据
                xs.insert(0, row[0])
                if row[fields[2]] < 0:
                    ysr.insert(0, 0)
                    ysg.insert(0, row[fields[2]])
                else:
                    ysr.insert(0, row[fields[2]])
                    ysg.insert(0, 0)
        except BaseException as ex:
            QMessageBox.information(self, '提示信息', '从数据库中取数据失败：{}。'.format(ex.args))
        conn.close()

        # 在图表中显示
        xs = xs[-20:]
        ysr = ysr[-20:]
        ysg = ysg[-20:]
        self.ax.clear()
        self.ax.bar(xs, ysr, facecolor='red')
        self.ax.bar(xs, ysg, facecolor='green')

        self.figure.autofmt_xdate()
        for label in self.ax.xaxis.get_ticklabels():
            label.set_rotation(45)

        self.canvas.draw()

    @pyqtSlot()
    def on_btnRefreshBlockHis_clicked(self):
        """刷新历史资金列表"""
        self.displayCurGNBlock(1)

    @pyqtSlot()
    def on_btnRefreshOneGNList_clicked(self):
        """刷新当前成分股"""
        self.displayCurGNBlock(2)

    @pyqtSlot()
    def on_twGNFundFlow1_itemSelectionChanged(self):
        """切换当前板块"""
        self.displayCurGNBlock(0)

    @pyqtSlot()
    def on_edtSearch_returnPressed(self):
        """在查找输入框中按了回国键"""
        self.on_btnSearch_clicked()

    @pyqtSlot()
    def on_btnSearch_clicked(self):
        """查找板块"""
        # QMessageBox.information(self, '提示信息', '查找板块。')

        # 要查找的字符串
        find_str = self.edtSearch.text()

        rowcount = self.twGNFundFlow1.rowCount()
        if rowcount <= 0:
            return

        row = self.twGNFundFlow1.currentRow()
        if row < 0:
            row = 0
        else:
            row = row + 1

        hasFind = -1
        for i in range(row, rowcount):
            item = self.twGNFundFlow1.item(i, 0)
            block_name = item.text()
            if block_name.find(find_str) >= 0:
                hasFind = i
                break

        if hasFind < 0:
            for i in range(row):
                item = self.twGNFundFlow1.item(i, 0)
                block_name = item.text()
                if block_name.find(find_str) >= 0:
                    hasFind = i
                    break

        if hasFind >= 0:
            self.twGNFundFlow1.setCurrentCell(hasFind, 0)

    @pyqtSlot()
    def on_btnRefreshGNFundFlow_clicked(self):
        """刷新资金流向"""
        # 重新下载当日和5日的板块资金流向
        QMessageBox.information(self, '提示信息', self.reloadGNFundFlow())

        # 显示当日和5日板块资金流
        self.displayGNFundFlow()

    @pyqtSlot()
    def on_btnGenStockPool_clicked(self):
        """股票池生成"""
        stock_pool = set()

        block_set = (self.GNBlocks | set(glv.gGNBlockGD)) - set(glv.gGNBlockPC)

        for block_name in block_set:
            file_name = r'Data\GNBlock\{}.csv'.format(block_name)
            if not os.path.exists(file_name):
                # 重新下载某个板块的成分股列表，并存入CSV文件
                self.reloadOneGNList(block_name)

            stock_data = pd.read_csv(file_name, dtype={'代码': str})
            stock_codes = set(stock_data['代码'])
            stock_pool = stock_pool | stock_codes

        with open(r'Data\gn_stock_pool.txt', 'w', encoding='utf-8') as f:
            for stock in stock_pool:
                f.write(stock)
                f.write('\n')

        QMessageBox.information(self, '提示信息', '股票池已生成，在Data\gn_stock_pool.txt文件中。')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = GnFundFlow()
    ui.show()
    sys.exit(app.exec_())
